/*
	Main test program
*/
#include <sched.h>
#include "ChipAwareContainer.h"
#include "Producer.h"
#include "Consumer.h"
using namespace std;

/*************** for debug only *************************/
/* debug print functions */

void printArr(int* arr, int len)
{
	for(int i = 0; i < len; i++)
	{
		cout << arr[i] << " ";
	}
	cout << endl;
}

void print2DArr(int** arr, int d1, int d2)
{
	for(int i = 0; i < d1; i++)
	{
		printArr(arr[i],d2);
	}
}

void printConfig()
{
	cout << "forceAssignment = " << Config::cpus->forceAssignment << endl;
	cout<< "prods:" << endl;
	printArr(Config::cpus->prods,Config::cpus->prodCoreNum);
	cout<< "cons:" << endl;
	printArr(Config::cpus->cons,Config::cpus->consCoreNum);
	cout<< "chips:" <<endl;
	print2DArr(Config::cpus->chips,Config::cpus->chipNum,Config::cpus->coresPerChip);
	cout << "prodThreadsNum = " << Config::cpus->prodThreadsNum << endl;
	cout << "consThreadsNum = " << Config::cpus->consThreadsNum << endl;
	cout << "peakLength = " << Config::peakLength << endl;
	cout << "timeBetweenPeaks = " << Config::timeBetweenPeaks << endl;
	cout << "timeToRun = " << Config::timeToRun << endl;
	cout << "containerType = " << Config::containerType << endl;
	cout << "perChipContainersNum = " << Config::perChipContainersNum << endl;
	cout << "perChipContainerType = " << Config::perChipContainerType << endl;
	/*
	cout << "contentionStatistics = " << Config::contentionStatistics << endl;
	cout << "exitsStatistics = " << Config::exitsStatistics << endl;
	cout << "consType = " << Config::consType << endl;
	cout << "prodType = " << Config::prodType << endl;
	cout << "shortTaskLength = " << Config::shortTaskLength << endl;
	cout << "longTaskRange = " << Config::longTaskRange << endl;
	cout << "consFailuresThreshold = " << Config::consFailuresThreshold << endl;
	cout << "quasiType = " << Config::quasiType << endl;
	cout << "quasiFactor = " << Config::quasiFactor << endl;
	cout << "qRandomTries = " << Config::qRandomTries << endl;
	*/
}
/**************************************************************/

/* print results - statistics gathered in the test */
void printStatistics(int numOfProducers, Producer* prods, int numOfConsumers, Consumer* cons, int peakLength)
{
	/* producers */
	list<long>* measurements = new list<long>();  // a list holding all the peak periods time measurements of all producers
	long numOfInserted = 0; // a total of all tasks inserted by all producers combined
	double sum = 0;  // sum of all peak periods durations of all the producers, converted into ms
	Statistics* prodStat = new Statistics();  // average CAS statistics for a producer
	
	/* consumers */
	long numOfRetrieved = 0;  // total number of tasks retrueved
	long maxWaitingTime = 0;  // maximal waiting time of a task (with valid insertion time fields) in the container
	double throughputSum = 0;  // total throughput (all consumers combined) in tasks/ms
	long waitingSum = 0;  // total waiting time of all tasks with valid insertion time fields
	long waitingCount = 0; // total number of tasks with valid insertion time fields that were retrieved
	Statistics* consStat = new Statistics();  // average CAS statistics for a consumer
	
	/* gather producers' statistics */
	if(numOfProducers > 0)
	{
		for(int i = 0; i < numOfProducers; i++)
		{
			numOfInserted += prods[i].getPeakLength() * prods[i].getTimeMeasurements()->size();
			list<long>::iterator it;
			for(it = prods[i].getTimeMeasurements()->begin(); it !=  prods[i].getTimeMeasurements()->end(); it++)
			{
				measurements->push_front(*it);
				sum += ((double)*it)/1000000;
			}
			Statistics* s = prods[i].getStatistics();
			prodStat->add(s);
		}
		prodStat->normalize(numOfProducers);
		double averageInsertionTime = sum/measurements->size(); // average burst length in [ms]
		cout << "Average insertion time (peak length) = " << averageInsertionTime << " ms" << endl;
		cout << "Total number of inserted tasks = " << numOfInserted << endl;
		cout << "Peak Insertion throughput = " << peakLength * numOfProducers *(1/averageInsertionTime) << endl;  // total insertion throughput (all producers combined) in tasks/ms
	}
	
	/* gather consumers' statistics */
	if(numOfConsumers > 0)
	{
		int exitsNum = 0;
		for(int i = 0; i < numOfConsumers; i++)
		{
			numOfRetrieved += cons[i].getNumOfTasks();
			
			/********** debug *********************************/
			/*cout << cons[i].getMaxWaitingTime();
			for(int j = 0; j < 6; j++){cout << " " << cons[i].longestWaitingTask[j];}
			cout << endl;*/
			//cout << "consumer: " << i << " queueNum: " << cons[i].debugInfo << endl;
			/************************************************/ 
			
			if(maxWaitingTime < cons[i].getMaxWaitingTime())
			{
				maxWaitingTime = cons[i].getMaxWaitingTime();
			}
			throughputSum += cons[i].getThroughput();
			waitingSum += cons[i].getWaitingMeasurementsSum();
			waitingCount += cons[i].getWaitingMeasurementsCount();
			exitsNum += cons[i].getExitsNum();
			Statistics* s = cons[i].getStatistics();
			consStat->add(s);
		}
		consStat->normalize(numOfConsumers);
		cout << "Total Number of retrieved tasks = " << numOfRetrieved << endl;
		cout << "System Throughput = " << throughputSum << endl;
		cout << "Maximal waiting time = " << maxWaitingTime << " ms" << endl;
		cout << "Average waiting time = " << ((double)waitingSum)/waitingCount << " ms" << endl;
		if(exitsNum >= 0)
		{
			cout << "Average exits num = " << ((double)exitsNum)/numOfConsumers << endl;
		}
		cout << "CAS statistics per producer:" << endl;
		prodStat->print();
		cout << "CAS statistics per consumer: " << endl;
		consStat->print();
	}
	delete measurements;
	delete prodStat;
	delete consStat;
}

/******* debug *****************/
/* arrays of consumer-bound cpus and producer-bound cpus : for affinity debug */
//int* consumerCpus;
//int* producerCpus;
/*******************************/

/* set thread affinity - bound the thread to a given CPU */
void assignToCPU(int cpu){
	pthread_t threadId = pthread_self();
	cpu_set_t cpuSet;
	CPU_ZERO(&cpuSet);
	CPU_SET(cpu,&cpuSet);
	int s = pthread_setaffinity_np(threadId,sizeof(cpu_set_t),&cpuSet);
	if(s != 0)
	{
		cout << "Thread Assignment Failed! exiting.." << endl;
		exit(-1);
	}
}

/* producer thread run */
void* prodRun(void* arg)
{
	Producer* p = (Producer*)arg;
	if(Config::cpus->forceAssignment)
	{
		int cpu = Config::producerToCore->find(p->id)->second;
		assignToCPU(cpu);
	}
	p->run();
	//producerCpus[p->id] = sched_getcpu(); // debug only
	return NULL;
}

/* consumer thread run */
void* consRun(void* arg)
{
	Consumer* c = (Consumer*)arg;
	if(Config::cpus->forceAssignment)
	{
		int cpu = Config::consumerToCore->find(c->id)->second;
		assignToCPU(cpu);
	}
	c->run();
	//consumerCpus[c->id] = sched_getcpu(); // debug
	return NULL;
}


int main(int argc, char* argv[])
{
	if(argc < 0)
	{
		cout << "need config file name" << endl;
		return -1;
	}
	// read configuration file
	Config::readConfiguration(argv[1]);
	int prodNum = Config::cpus->prodThreadsNum;
	int consNum = Config::cpus->consThreadsNum;

	/******* debug *****************/
	//consumerCpus = new int[consNum];
	//producerCpus = new int[prodNum];
	/*****************************/
	
	/* create container - this section needs to be manually changed when adding a new container type to the project
		all containers must inherit from TaskContainer */
	TaskContainer* container;
	if(Config::containerType.compare("containers.ChipAwareContainer") == 0)
	{
		container = new ChipAwareContainer(Config::cpus->consThreadsNum);
	}
	else if(Config::containerType.compare("containers.WorkStealingContainer") == 0)
	{
		container = new WorkStealingContainer(Config::cpus->consThreadsNum);
	}
	else if(Config::containerType.compare("containers.ConcurrentListContainer") == 0)
	{
		container = new ConcurrentListContainer();
	}
	else
	{
		cout << "no container matches the container name in config file: " << Config::containerType << endl;
		return -1;
	}
	//container->setConsumersNum(consNum);

	//prepare producers
	Producer* producers = new Producer[prodNum];
	for(int i = 0; i < prodNum; i++)
	{
		producers[i].setContainer(container);
		producers[i].id = i;
	}
	pthread_t* producerThreads = new pthread_t[prodNum];

	// prepare consumers
	Consumer* consumers = new Consumer[consNum];
	for(int i = 0; i < consNum; i++)
	{
		consumers[i].setContainer(container);
		consumers[i].id = i;
	}
	pthread_t* consumerThreads = new pthread_t[consNum];

	// add threads to Config and initialize container
	int* producerIds = new int[prodNum];
	for(int i = 0; i < prodNum; i++)
	{
		producerIds[i] = producers[i].id;
	}
	int* consumerIds = new int[consNum];
	for(int i = 0; i < consNum; i++)
	{
		consumerIds[i] = consumers[i].id;
	}
	Config::init(producerIds,consumerIds);
	container->init();

	//create and start threads
	for(int i = 0; i < prodNum; i++)
	{
		pthread_create(&producerThreads[i],NULL,prodRun,(void*)&producers[i]);
	}
	for(int i = 0; i < consNum; i++)
	{
		pthread_create(&consumerThreads[i],NULL,consRun,(void*)&consumers[i]);
	}

	// let producers and consumers do their thing...
	usleep(1000*Config::timeToRun);
	Producer::stop();
	Consumer::stop();
	
	// wait for child threads
	for(int i = 0; i < prodNum; i++)
	{
		pthread_join(producerThreads[i],NULL);
	}
	for(int i = 0; i < consNum; i++)
	{
		pthread_join(consumerThreads[i],NULL);
	}

	printStatistics(prodNum,producers,consNum,consumers,Config::peakLength);
	
	/***** debug ***************/
	/*
	cout << "producer cpus:" << endl;
	printArr(producerCpus,prodNum);
	cout << "consumer cpus:" << endl;
	printArr(consumerCpus, consNum);
	delete[] producerCpus;
	delete[] consumerCpus;
	*/
	/**************************/
	// free allocated memory
	Config::freeMem();
	delete[] producers;
	delete[] producerThreads;
	delete[] producerIds;
	delete[] consumers;
	delete[] consumerThreads;
	delete[] consumerIds;
	delete container;
	return 0;
}




